package org.mtmi.ui.scenes;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Shell;
import org.mtmi.io.SceneWriter;
import org.mtmi.tuio.listeners.DragListener;
import org.mtmi.tuio.listeners.SingleTapListener;
import org.mtmi.tuio.listeners.TUIODragEvent;
import org.mtmi.tuio.listeners.TUIOTapEvent;
import org.mtmi.ui.GraphicUtil;
import org.mtmi.ui.IMG;
import org.mtmi.ui.M;
import org.mtmi.ui.actions.Action;
import org.mtmi.ui.actions.IAction;
import org.mtmi.ui.actions.IActionListener;
import org.mtmi.ui.actions.IRetargetableAction;
import org.mtmi.ui.controls.Control;
import org.mtmi.ui.controls.GCSave;
import org.mtmi.ui.controls.ITouchableControl;
import org.mtmi.ui.controls.ToggleButton;
import org.mtmi.ui.controls.ToolBar;
import org.mtmi.ui.controls.TouchControlEvent;
import org.mtmi.ui.controls.listeners.ControlBoundsEvent;
import org.mtmi.ui.controls.listeners.ControlBoundsListener;
import org.mtmi.ui.controls.listeners.ISelectionListener;
import org.mtmi.ui.controls.listeners.SelectionEvent;
import org.mtmi.ui.drawables.IDrawable;
import org.mtmi.ui.edit.AbstractSourceControlConnector;
import org.mtmi.ui.edit.IControlEditor;
import org.mtmi.ui.edit.ResizeHandle;
import org.mtmi.ui.scenes.actions.ZOrderAction;

public class Scene extends Canvas 
  implements PaintListener, ITouchableControl, SingleTapListener, DragListener
{
	/** default edit mode, control are selectable, moveable, resizeable, etc ... */
	public static final int EDIT_CONTROL = 0;
	/** edit mode where the Z stack of selected control of scene is edited */
	public static final int EDIT_Z_STACK = 1;
	
	/** dimension for miniature */
	public static final int L_MINIATURE = 200;
	
	//list of scene controls
	private List<Control> controls;
	//list of control added to edit scene or popup menu ...
	private List<Control> editors;
	//- debug show the last touch point
	private Point point;
	private int width;
	private int height;
	private boolean editing;
	private int edit_mode; //see EDIT_xxx constants 
	private List<Control> selection; //selected control to edit
	private ArrayList<ISelectionListener> lis_selection;
	private ToolBar tb_edit_control; //toolbar for control edit actions.
	private List<AbstractSourceControlConnector> connectors; //editing connectors
	private Image miniature; //scene miniature, created on demand.
	private String filename; //scene file name used to save scene, may be null for unsaveable scene
	//the name of background image.
	private String background_image_key;
	
	public Scene( Shell parent ) {
		super(parent, SWT.DOUBLE_BUFFERED);
		editing = false;
		selection = new LinkedList<Control>();
		controls = new ArrayList<Control>();
		editors = new ArrayList<Control>();
		setBackground( COLOR.sceneBg );
		addPaintListener(this);
		//- debug show the last touch point
		point = new Point(-1,-1);

		//- store the dimension of the scene in order to access from non UI thread (in getTouchableControl())
		width = getClientArea().width;
		height = getClientArea().height;
		
		addControlListener( new ControlAdapter() {
			@Override
			public void controlResized(ControlEvent arg0) {
				width = getClientArea().width;
				height = getClientArea().height;
				disposeMiniature();
			}
		});
		
		gc = new GC( this );
		addDisposeListener( new DisposeListener() {
			@Override
			public void widgetDisposed(DisposeEvent arg0) {
				gc.dispose(); gc=null;
				disposeMiniature();
			}
		});
	}

	/**
	 * Add a control to this scene.
	 * @param control the control to add.
	 */
	public void addControl( Control control ) {
		if( control.isSceneControl() ) {
			controls.add( control );
		} else {
			editors.add( control );
		}
	}
	
	/**
	 * @return controls of scene.
	 * @param editor true to get scene editor controls, false for others
	 */
	public Control[] getControls( boolean editor ) {
		if( editor ) {
			//don't want caller to modify controls without calling removeControl() or addControl()
			return editors.toArray( new Control[ editors.size() ] );
		} else {
			//don't want caller to modify controls without calling removeControl() or addControl()
			return controls.toArray( new Control[ controls.size() ] );
		}
	}
	
	/**
	 * Remove a control from this scene, control may be added later.
	 * @param control the control to remove.
	 */
	public void removeControl( Control control ) {
		if( control.isSceneControl() ) {
			controls.remove( control );
		} else {
			editors.remove( control );
		}
	}

	/**
	 * Dispose a control to this scene.
	 * @param control the control to dispose.
	 */
	public void disposeControl( Control control ) {
		if( control.isSceneControl() ) {
			controls.remove( control );
		} else {
			editors.remove( control );
		}
		control.notifyDisposedListeners();
	}
	
	private GC gc; //keep a GC, because it can't be created in getTouchableControl() due to non UI thread usage.
	
	/** @return a gc usable for layout and computation, but NOT FOR PAINTING */
	public GC getGC() {
		return gc;
	}
	
	/**
	 * Returns the control under the given normalized coordinates or the scene itself if there is not.
	 * @param cx
	 * @param cy
	 * @return
	 */
	public ITouchableControl getTouchableControl(float cx, float cy) {
		//- debug show the last touch point
		point.x = (int) (cx*width); 
		point.y = (int) (cy*height);
		TouchControlEvent e = new TouchControlEvent( gc, point.x, point.y );
		
		for( int i=editors.size()-1; i>=0; i-- ) {
			Control c = editors.get( i );
			if(c instanceof ITouchableControl) {
				ITouchableControl control = (ITouchableControl) c;
				if( control.containsTouch( e ) ) {
					return control;
				}
			}
		}
		
		if( editing ) return this;
		
		
		//- highest controls are at the end of the list (chronological addition)
		for( int i=controls.size()-1; i>=0; i-- ) {
			Control c = controls.get( i );
			if(c instanceof ITouchableControl) {
				ITouchableControl control = (ITouchableControl) c;
				if( control.containsTouch( e ) ) {
					/*if( editing ) {
						if( selection.contains( control ) ) {
							selection.remove( c );
						} else {
							selection.add( c );
						}
						redraw();
						touched_control_is_edition = true; ///see tap/drag
						return this; //don't want scene menu to appear
					} else */{
						return control;
					}
				}
			}
		}
		//- if no control found this is the scene
		return this;
	}

	/** @return scene miniature, caller MUST NOT dispose returned image */
	public Image getMiniature() 
	{
		if( miniature!=null ) return miniature;
		
long t0=System.currentTimeMillis();		
		Image img = new Image( getDisplay(), width, height );
		GC gc = new GC(img);
		try {
			gc.setBackground( getBackground() );
			gc.fillRectangle( 0, 0, width, height );
			Image bi = getBackgroundImage();
			if( bi!=null ) {
				gc.drawImage( bi, 0, 0 );
			}
			//paint only "real" controls, (not editors)
			for( Control control : controls ) {
				control.paint(gc);
			}
		} finally {
			gc.dispose();
		}
		
		int w, h;
		if( width >= height ) {
			w = L_MINIATURE;
			h = L_MINIATURE*height/width;
		} else {
			w = L_MINIATURE*width/height;
			h = L_MINIATURE;
		}
		
		Rectangle ri = img.getBounds();
		miniature = new Image( getDisplay(), w, h );
		gc = new GC( miniature );
		try {
			gc.drawImage( img, 0,0,ri.width,ri.height, 0,0, w, h );
		} finally {
			gc.dispose();
		}
		img.dispose();
		
long d = System.currentTimeMillis()-t0;
System.out.println(">> GET SCENE IMAGE duration="+d+"ms");
		return miniature;
	}
	
	private void disposeMiniature() {
		if( miniature!=null ) {
			miniature.dispose();
			miniature = null;
		}
	}
	
	
	int pcount = 0;
	long pcumul=0;
	
	@Override
	public void paintControl(PaintEvent e) {
long t0=System.currentTimeMillis(); 
        GCSave save = new GCSave( e.gc );
        GC gc = e.gc;
        
        if( !editing ) {
        	for( Control control : controls ) {
//TODO:remove			Transform t = new Transform(e.display);
//			Point loc = control.getLocation();
//			t.translate(loc.x, loc.y);
//			gc.setTransform(t);
//			save.restoreGC( gc );
        		control.paint(gc);
//			t.invert();
//			gc.setTransform(t);
        	}
        }
        else {
        	//draw unselected control (grayed)
        	for( Control control : controls ) {
        		//TODO:remove			Transform t = new Transform(e.display);
        		//        					Point loc = control.getLocation();
        		//        					t.translate(loc.x, loc.y);
        		//        					gc.setTransform(t);
        		//        					save.restoreGC( gc );
        		if( edit_mode != EDIT_CONTROL || !selection.contains( control ) ) {
        			control.paint(gc);
        		}
        		//        					t.invert();
        		//        					gc.setTransform(t);
        	}
			//gray scene:
			gc.setBackground( COLOR.grey30 );
			gc.setAlpha( 150 );
			gc.fillRectangle( 0, 0, width, height );
			gc.setAlpha( 255 );
			
			//draw selected control (ungrayed), except for Z_ORDER editing
			if( edit_mode == EDIT_CONTROL ) {
				for( Control c : selection ) {
					c.paint( gc );
				}
			}
        }

        for( Control control : editors ) {
        	control.paint( gc );
        }
		
//		e.gc.setTransform(null);
        
		//- debug show the last touch point
		if(point.x > 0 && point.y > 0) {
			e.gc.setBackground( COLOR.red );
			e.gc.fillArc(point.x-5, point.y-5, 10, 10, 0, 360);
		}
long d = System.currentTimeMillis()-t0;
pcumul+=d;
pcount++;
if( pcount>=20 ) {
	long avg = pcumul/pcount;
	System.out.println(" Scene.paintControl() average="+avg+"ms  for "+pcount+" draw");
	pcumul=0;
	pcount=0;
}
        

		//- debug show the moving point and pixel coordinates
		/*e.gc.setLineWidth(0);
		Point p = getDisplay().getCursorLocation();
		p = toControl( p );
		//e.gc.setAlpha( 150 );
		e.gc.setBackground(ColorManager.getInstance().getColor(COLOR.red));
		e.gc.fillOval(p.x-3, p.y-3, 6, 6 );
		e.gc.drawText( "["+p.x+","+p.y+"]", p.x+10, p.y );*/
	}

	/**
	 * Returns the list of toggle (radio) buttons that belong to the same group.
	 * @param groupId the id of the group to search content.
	 * @return the list of toggle (radio) buttons that belong to the same group.
	 */
	public List<ToggleButton> getButtonsOfSameGroup(int groupId) {
		ArrayList<ToggleButton> l = new ArrayList<ToggleButton>();
		for(Control c : controls) {
			if(c instanceof ToggleButton) {
				if(((ToggleButton)c).getGroupId() == groupId) {
					l.add((ToggleButton) c);
				}
			}
		}
		return l;
	}
	
	/**
	 * Returns the actual position of the given normalized point 
	 * @param x horizontal coordinate described as float (between 0 and 1). 
	 * @param y vertical coordinate described as float (between 0 and 1). 
	 * @return the corresponding point in the scene
	 */
	public Point getEventPosition(float x, float y) {
		return new Point((int)(x*getSize().x), (int)(y*getSize().y));
	}

	@Override
	public boolean containsTouch( TouchControlEvent e ) {
		//- never called, but all scene is covered by scene menu.
		return true; 
	}
	
	@Override
	public void paint(GC gc) {
		// do nothing, scene menu is painted directly by paintControl()
	}

	private int evdx, evdy; //for edition:move
	private int state; //0:none 1:object tapped, 2:moving objects
	
	private static final String D_WAS_SELECTED_BEFORE_TAP_DOWN="selBeforeTapDn@Scene"; //$NON-NLS-1$
	
	@Override
	public void tapDown( TUIOTapEvent event ) {
		
		if( editing ) {
			//may select/unselect control under tap event:
			TouchControlEvent e = new TouchControlEvent( gc, event.ex, event.ey );
			boolean consumed=false;
			for( int i=controls.size()-1; i>=0; i-- ) {
				Control c = controls.get( i );
				if(c instanceof ITouchableControl) {
					ITouchableControl control = (ITouchableControl) c;
					if( control.containsTouch( e ) ) {
						//see tapUp for object release
						boolean selected = selection.contains( control );
						if( !selected ) {
							addToSelection( c );
							redraw();
						}
						c.setData( D_WAS_SELECTED_BEFORE_TAP_DOWN, selected );
						consumed=true;
						break;
					}
				}
			}
			if( consumed ) {
				if( selection.size()>0 ) {
					//keep reference of first selected for edition:move
					Point location= selection.get(0).getLocation();
					evdx = event.ex-location.x;
					evdy = event.ey-location.y;
					state=1;
				}
				return ;
			} else { //tap outside control: reset selection
				if( hasSelection() ) {
					setSelection( null );
				}
			}
		}
		
		state=0;
	/*	if( touched_control_is_edition ) {
			Point location= selection.get(0).getLocation();
			evdx = event.ex-location.x;
			evdy = event.ey-location.y;
		}
		else*/
		/*TODO: avoid menu openind: it's deprecated 
		if(menu != null) {
			if(!menu.isOpen()) {
				menu.open( event.ex, event.ey);
			} else {
				//- cancel context menu.
				menu.close();
			}
		}*/
	}

	/** @return the control at given location */
	public ITouchableControl getControlAt( int x, int y )
	{
		TouchControlEvent e = new TouchControlEvent( gc, x, y );
		//because some scene control editor may provide interesting thing (ie connection)
		//include them in getControl;
		for( int i=editors.size()-1; i>=0; i-- ) {
			Control c = editors.get( i );
			if( c instanceof ITouchableControl ) {
				ITouchableControl control = (ITouchableControl) c;
				if( control.containsTouch( e ) ) {
					return control;
				}
			}
		}
		
		// "normal" controls
		for( int i=controls.size()-1; i>=0; i-- ) {
			Control c = controls.get( i );
			if( c instanceof ITouchableControl ) {
				ITouchableControl control = (ITouchableControl) c;
				if( control.containsTouch( e ) ) {
					return control;
				}
			}
		}
		
		return null;
	}
	
	@Override
	public void tapUp(TUIOTapEvent event) {
		if( editing && (state==1) ) {
			state=0;
			TouchControlEvent e = new TouchControlEvent( gc, event.ex, event.ey );
			boolean consumed=false;
			for( int i=controls.size()-1; i>=0; i-- ) {
				Control c = controls.get( i );
				if(c instanceof ITouchableControl) {
					ITouchableControl control = (ITouchableControl) c;
					if( control.containsTouch( e ) ) {
						if( c.getData( D_WAS_SELECTED_BEFORE_TAP_DOWN, false ) ) {
							if( selection.contains( control ) ) {
								removeFromSelection( c );
								redraw();
							}
						}
						consumed=true;
						break;
					}
				}
			}
			if( consumed ) {
				return ;
			}
		}
		state=0;
	}
	
	@Override
	public void drag(TUIODragEvent event) {
//		if( touched_control_is_edition && selection.size()>0 ) {
		if( (state==1||state==2) && selection.size()>0 ) {
			state=2;
			//Edition: move the control
			Point location= selection.get(0).getLocation();
			int lx = event.ex - evdx;
			int ly = event.ey - evdy;
			if( lx!=location.x || ly!=location.y ) {
				int dx = location.x-lx;
				int dy = location.y-ly;
				
				//constraint moves: we want selected control to stay inside scene bounds
				Rectangle ca = getClientArea();
				for( Control c : selection ) {
					Point l = c.getLocation();
					Point s = c.getSize();
					int nlx = l.x-dx, nly = l.y-dy;
					if( nlx < 0 || nlx+s.x > ca.width ) {
						dx = 0;
						if( dy==0 ) break;
					}
					if( nly < 0 || nly+s.y>ca.height ) {
						dy = 0;
						if( dx==0 ) break;
					}
				}
				if( dx !=0 || dy!=0 ) {
					for( Control c : selection ) {
						Point l = c.getLocation();
						c.setLocation( l.x-dx, l.y-dy );
					}
					Point l = rh_topleft.getLocation();
					rh_topleft.removeControlBoundsListener( rh_topleft_listener );
					rh_topleft.setLocation( l.x-dx, l.y-dy );
					rh_topleft.addControlBoundsListener( rh_topleft_listener );
					l = rh_btmright.getLocation();
					rh_btmright.removeControlBoundsListener( rh_btmright_listener );
					rh_btmright.setLocation( l.x-dx, l.y-dy );
					rh_btmright.addControlBoundsListener( rh_btmright_listener );
					updateEditControlToolBarLocation();
					updateZOrderActionOfGlobalActionToolbar();
					redraw();
				}
			}
		}
	}
	
	private ResizeHandle rh_topleft, rh_btmright;
	
	/** change selected control to the list, null or empty list to clear selection, scene is not redrawn*/
	public void setSelection( List<Control> c ) 
	{
		// mark old selection bounds to be redrawn:
		if( selection!=null ) {
			for( Control x : selection ) {
				x.redraw();
			}
		}
		selection.clear();
		if( c!= null ) {
			selection.addAll( c );
		}
		//mark new selection to be redrawn:
		if( selection!=null ) {
			for( Control x : selection ) {
				x.redraw();
			}
		}
		if( selection.size()== 0 ) {
			clearHandles();
			clearEditControlToolBar();
		}
		else {
			createHandles();
			updateResizeHandles();
			updateEditControlToolBar();
		}
		notifySelectionChanged();
	}
	
	private void addToSelection( Control c ) 
	{
		selection.add( c );
		if( edit_mode == EDIT_CONTROL ) {
			createHandles();
			updateResizeHandles();
			updateEditControlToolBar();
		}
		notifySelectionChanged();
	}
	
	private void removeFromSelection( Control c ) {
		selection.remove( c );
		if( edit_mode == EDIT_CONTROL ) {
			if( selection.size()==0 ) {
				clearHandles();
				clearEditControlToolBar();
			} else {
				updateResizeHandles();
				updateEditControlToolBar();
			}
		}
		notifySelectionChanged();
	}
	
	private void updateResizeHandles() {
		Rectangle r = new Rectangle(0,0,0,0);
		selection.get( 0 ).getBounds( r );
		Rectangle t = new Rectangle(0,0,0,0);
		for( int i=1; i<selection.size(); i++ ) {
			Control cc = selection.get( i );
			t = cc.getBounds( t );
			System.out.println("  cc.b="+t);
			GraphicUtil.expandRect( r, t );
		}
		System.out.println(">> updateResizeHandle r="+r);
		Point s = rh_topleft.getSize();
		rh_topleft.removeControlBoundsListener( rh_topleft_listener );
		rh_topleft.setLocation( r.x, r.y );
		rh_topleft.addControlBoundsListener( rh_topleft_listener );
		s = rh_btmright.getSize();
		rh_btmright.removeControlBoundsListener( rh_btmright_listener );
		rh_btmright.setLocation( r.x+r.width, r.y+r.height );
		rh_btmright.addControlBoundsListener( rh_btmright_listener );
	}
	
	private ControlBoundsListener rh_topleft_listener, rh_btmright_listener;
	
	private void createHandles()
	{
		if ( rh_topleft==null ) {
			rh_topleft = new ResizeHandle( this, M.TOP|M.LEFT );
			rh_topleft_listener = new ControlBoundsListener( ) {
				@Override
				public void controlBoundsChanged( ControlBoundsEvent e ) {
					Point l = rh_topleft.getLocation();
					int dx = l.x-e.ox, dy = l.y-e.oy;
					topLeftHandleMoved( dx, dy );
				}
			};
			rh_topleft.addControlBoundsListener( rh_topleft_listener );
			rh_btmright = new ResizeHandle( this, M.BOTTOM|M.RIGHT );
			rh_btmright_listener = new ControlBoundsListener( ) {
				@Override
				public void controlBoundsChanged( ControlBoundsEvent e ) {
					Point l = rh_btmright.getLocation();
					int dx = l.x-e.ox, dy = l.y-e.oy;
					bottomRightHandleMoved( dx, dy );
				}
			};
			rh_btmright.addControlBoundsListener( rh_btmright_listener );
		}
	}
	private void clearHandles()
	{
		if( rh_topleft!= null ) {
			rh_topleft.dispose(); rh_topleft=null;
			rh_btmright.dispose(); rh_btmright=null;
			rh_topleft_listener = null;
			rh_btmright_listener=null;
		}
	}
	private void clearEditControlToolBar() {
		if( tb_edit_control != null ) {
			tb_edit_control.dispose();
			tb_edit_control=null;
		}
		if( connectors!=null ) {
			for( AbstractSourceControlConnector c : connectors ) {
				c.dispose();
			}
			connectors.clear();
			connectors=null;
		}
	}
	
	/** mandatory when control bounds changed, the enable state of ZOrderAction must be changed */
	private void updateZOrderActionOfGlobalActionToolbar() {
		GlobalActionToolBar gat = getGlobalActionToolbar();
		List<IAction> actions =  gat.getActions();
		for( IAction a : actions ) {
			if( a instanceof ZOrderAction ) {
				((ZOrderAction)a).sceneControlBoundsChanged();
			}
		}
	}
	
	private void topLeftHandleMoved( int dx, int dy )
	{
		if( selection.size()==1 ) { //TODO: parce que c'est plus simple ..
			Control c = selection.get(0);
			Point l = c.getLocation();
			Point s = c.getSize();
			int r = l.x+s.x, b = l.y+s.y;
			int x = l.x+dx, y = l.y+dy;
			c.setBounds( x, y, r-x, b-y );
			//TODO ... int lx = c.getLocation().x, ly = c.getLocation().y;
		} else {
//TODO: if ok: merge with sel.size==1			
			for( Control c: selection ) {
				Point l = c.getLocation();
				Point s = c.getSize();
				int r = l.x+s.x, b = l.y+s.y;
				int x = l.x+dx, y = l.y+dy;
				c.setBounds( x, y, r-x, b-y );
			}
		}
		
		redraw(); //note: refine area to redraw is a good idea for performances.
		
		updateZOrderActionOfGlobalActionToolbar();
	}
	
	private void bottomRightHandleMoved( int dx, int dy )
	{
		if( selection.size()==1 ) { //TODO: parce que c'est plus simple ..
			Control c = selection.get(0);
			Point s = c.getSize();
			c.setSize( s.x+dx, s.y+dy );
		} else {
			//TODO: if ok: merge with sel.size==1			
			for( Control c: selection ) {
				Point s = c.getSize();
				c.setSize( s.x+dx, s.y+dy );
			}
			/*TODO: completement KO:
			Point np = rh_btmright.getLocation();
			Point tl = rh_topleft.getLocation();
			int obrx = np.x-dx;
			int obry = np.y-dy;
//			int ow = np.x-dx-tl.x; 
//			int oh = np.y-dy-tl.y;
			for( Control c: selection ) {
				Rectangle r = c.getBounds( null );
				if( obrx!=tl.x ) {
					int nx = r.x+Math.round( ((r.x-tl.x)*dx)/(float)(obrx-tl.x) );
					int nr = r.x+r.width+Math.round( ((r.x+r.width-tl.x)*dx)/(float)(obrx-tl.x) );
					if( nr==nx ) nr=nx+1;
					r.x = nx;
					r.width = nr-nx;
				}
				if( obry!=tl.y ) {
					int ny = r.y+Math.round( ((r.y-tl.y)*dy)/(float)(obry-tl.y) );
					int nb = r.y+r.height+Math.round( ((r.y+r.height-tl.y)*dy)/(float)(obry-tl.y) );
					r.y = ny;
					r.height= nb-ny;
				}
				c.setBounds( r );
			}
			System.out.println("TODO:  autres ... (SCENE)"); */
		}
		redraw(); //note: refine area to redraw is a good idea for performances.
		updateEditControlToolBarLocation();
		updateZOrderActionOfGlobalActionToolbar();
	}
	
	/** Change background image key, a key usable by IMG.Get() methods ... */
	public void setBackgroundKey( String key ) {
		background_image_key = key;
		if( key==null ) {
			setBackgroundImage( null );
		} else {
			setBackgroundImage( IMG.Get( key ) );
		}
	}
	
	/** @return the current image key used as scene background */
	public String getBackgroundKey() { return background_image_key; }
	
	/** @return true if scene is in edit mode */
	public boolean isEditing() { return editing; }

	/** Change edit mode status of scene */
	public void setEditing( boolean ed ) {
		if( editing!= ed ) {
			editing = ed;
			if( !editing ) {
				selection.clear();
				clearHandles();
				clearEditControlToolBar();
				disposeMiniature(); //edit mode may change scene...
				saveScene();
			}
			if( SceneManager.getDefault().getScene() == this ) {
				redraw();
			}
		}
	}
	
	/** 
	 * @return GlobalActionToolbar currently attached to this scene, null is there are none
	 * (not in edit mode, this scene is in edit mode but another one is displayed, or edit mode
	 * does ot use G.A.T. ).
	 */
	public GlobalActionToolBar getGlobalActionToolbar() {
		for( Control c : editors ) { //GAT is part of control editors
			if( c instanceof GlobalActionToolBar ) {
				return (GlobalActionToolBar)c;
			}
		}
		return null;
	}
	
	/** @current current edit mode, even if scene is "playing" control */
	int getEditMode() {
		if( editing ) return edit_mode; else return EDIT_CONTROL;
	}
	
	/** Must be called only by ZOrderToolBar */
	public void setEditMode( int _edit_mode ) {
		if( edit_mode==_edit_mode ) return ;
		edit_mode = _edit_mode;
		switch( edit_mode ) {
		case EDIT_Z_STACK : {
			GlobalActionToolBar gat = getGlobalActionToolbar();
			if( gat!=null ) gat.setVisible( false );
			clearHandles();
			clearEditControlToolBar();
			ZOrderToolBar tb = new ZOrderToolBar( this, 0 );
			
			break; }
		case EDIT_CONTROL: {
			GlobalActionToolBar gat = getGlobalActionToolbar();
			if( gat!=null ) gat.setVisible( true );
			if( hasSelection() ) {
				createHandles();
				updateResizeHandles();
				updateEditControlToolBar();
			}
			break; 	}
		default: 
			throw new Error("unknown edit mode: "+edit_mode );
		}
		redraw();
	}
	
	private void saveScene()
	{
		SceneWriter saver = new SceneWriter( this );
		saver.save();
	}
	
	/** @return the index of control (not work for scene editor control) */
	public int getIndexOf( Control c ) {
		return controls.indexOf( c );
	}
	
	/** @return the number of editor or 'normal' controls */
	public int getControlCount( boolean editor ) {
		if( editor ) {
			return editors.size();
		} else {
			return controls.size();
		}
	}
	
	/** @return a copy of the current selection, caller may modify returned list and call {@link #setSelection(List)} */
	public List<Control> getSelection() { 
		ArrayList<Control> cpy = new ArrayList<Control>();
		if( selection!= null ) {
			cpy.addAll( selection );
		}
		return cpy; 
	}
	
	/** @return true if there are at least one control selected */
	public boolean hasSelection() { return selection!=null && selection.size()>0; }
	
	/**
	 * only selectionChanged() is called.
	 * @param lis
	 */
	public void addSelectionListener( ISelectionListener lis ) {
		if( lis_selection ==null ) {
			lis_selection = new ArrayList<ISelectionListener>();
		}
		lis_selection.add( lis );
	}
	
	public void removeSelectionListener( ISelectionListener lis ) {
		if( lis_selection !=null ) {
			lis_selection.remove( lis );
		}
	}
	
	private void notifySelectionChanged() {
		if( lis_selection!=null ) {
			List<Control> cpy = getSelection();
			SelectionEvent e = new SelectionEvent( this, cpy );
			ISelectionListener lis[] = lis_selection.toArray( new ISelectionListener[lis_selection.size()] );
			for( ISelectionListener l : lis ) {
				l.selectionChanged( e );
			}
		}
	}
	
	private void createEditControlToolBar( List<? extends IAction> actions ) {
		tb_edit_control = new ToolBar( this, M.VERTICAL|M.SCENE_EDITOR_CONTROL );
		tb_edit_control.setForeground( COLOR.yellow );
		tb_edit_control.setBackground( COLOR.dark_yellow );
		for( int i=0; i<actions.size(); i++ ) {
			tb_edit_control.addAction( actions.get(i), i==actions.size()-1 );
		}
		updateEditControlToolBarLocation();
	}
	
	private void updateEditControlToolBarLocation() {
		if( tb_edit_control==null ) return ;
		//try on the left of btmright resize handle:
		Rectangle r = rh_btmright.getBounds( null );
		Point l = rh_topleft.getLocation();
		Point sz = tb_edit_control.getSize();
		int x = r.x + 5 ;
		int y = l.y ;
		//center verticaly
		if( y+sz.y+5 > r.y ) {
			y = l.y-(sz.y+5-(r.y-l.y))/2;
		}
		//toolbar over rh_btmright ?
		if( y+sz.y+5 > r.y ) x += r.width+5;
		if( x+sz.x+5 > width ) {
			//limit location to scene bounds:
			x = width-sz.x-5;
		}
		else if( x < 5 ) {
			x = 5;
		}
		if( y+sz.y+5 > height ) {
			y = height-sz.y-5;
		} else if( y < 5 ) {
			y = 5;
		}
		tb_edit_control.setLocation( x, y );
	}
	
	/** update edit control toolbar using selection */
	private void updateEditControlToolBar() {
		if( selection.size()==0 ) {
			clearEditControlToolBar();
		}
		else if( selection.size()==1 ) {
			//all action from control are allowed:
			Control sel = selection.get( 0 );
			IControlEditor editor = (IControlEditor)sel.getAdapter( IControlEditor.class, null );
			clearEditControlToolBar();
			if( editor!= null ) {
				List<IAction> actions = editor.getControlActions();
				if( actions!=null && actions.size()>0 ) {
					for( IAction a : actions ) {
						if( a instanceof IRetargetableAction ) {
							((IRetargetableAction)a).setTarget( sel );
						}
					}
					createEditControlToolBar( actions );
				}
				List<AbstractSourceControlConnector> cnc = editor.getControlConnectors();
				if( cnc!=null && cnc.size()>0 ) {
					connectors = new ArrayList<AbstractSourceControlConnector>();
					connectors.addAll( cnc );
				}
			}
		}
		else {
			//action must be retargetable AND shared by all selected controls
			ArrayList<IRetargetableAction> actions = new ArrayList<IRetargetableAction>();
			ArrayList<Integer> count = new ArrayList<Integer>();
			boolean first_control = true;
			for( Control sel : selection ) {
				IControlEditor editor = (IControlEditor)sel.getAdapter( IControlEditor.class, null );
				if( editor != null ) {
					List<IAction> la = editor.getControlActions();
					if( la!=null ) {
						for( IAction a : la ) {
							if( a instanceof IRetargetableAction ) {
								IRetargetableAction ra = (IRetargetableAction)a;
								if( first_control ) {
									if( ra.isTargetHandled( selection ) ) {
										actions.add( (IRetargetableAction)a );
										count.add( 1 );
									}
								} else {
									int idx = actions.indexOf( a );
									if( idx < 0 /*TODO: see above: || !actions.get( idx ).isTargetHandled( sel )*/ ) {
										//TODO: this action is not added, other one are kept
										//TODO: actions.clear(); //this action is shared by this control
										//break;
									} else {
										int c = count.remove( idx );
										count.add( idx, c+1 );
									}
								}
							} else {
								//TODO: this action is not added, other one are kept
								//actions.clear(); //all action must be retargetable
								//break;
							}
						}
					} else {
						//selection control doesn't provide editor, nor actions: reset all
						actions.clear(); //there is a uneditable control selected
						break;
					}
				} else {
					//selection control doesn't provide editor, nor actions: reset all
					actions.clear(); //there is a uneditable control selected
					break;
				}
				first_control = false;
			}
			//keep only actions that are shared by all selected control
			for( int i=0; i<actions.size();  ) {
				if( count.get(i) != selection.size() ) {
					actions.remove( i );
					count.remove( i );
					continue;
				}
				i++;
			}
			//now can update control toolbar
			clearEditControlToolBar();
			if( actions.size()>0 ) {
				//action must be retargetted to selection:
				final List<Control> sel = selection;
				List<IAction> na = new ArrayList<IAction>();
				for( IRetargetableAction a : actions ) {
//TODO: old code:					na.add( new SelectionBroadcastAction( a, sel ) );
					a.setTarget( sel );
					na.add( a );
				}
				createEditControlToolBar( na );
			}
			
			//connectors: gather all of them
			for( Control sel : selection ) {
				IControlEditor editor = (IControlEditor)sel.getAdapter( IControlEditor.class, null );
				if( editor!=null ) {
					List<AbstractSourceControlConnector> cnc = editor.getControlConnectors();
					if( cnc!=null && cnc.size()>0 ) {
						if( connectors==null ) {
							connectors = new ArrayList<AbstractSourceControlConnector>();
						}
						connectors.addAll( cnc );
					}
				}
			}
		}
		if( tb_edit_control!=null ) {
			updateEditControlToolBarLocation();
		}
	}
	
	private static class SelectionBroadcastAction extends Action implements IActionListener
	{
		private IRetargetableAction action;
		private List<Control> selection;
		public SelectionBroadcastAction( IRetargetableAction a, List<Control> s ) {
			super( a.getType() );
			action = a;
			selection = s;
			if( action instanceof Action ) {
				((Action)action).addActionListener( this ); //TODO: this listener must be removed !!!
			}
		}
		
		@Override
		public IDrawable getImage() { return action.getImage(); }
		@Override
		public String getText() { return action.getText(); }
		@Override
		public boolean isEnabled() {
			//enabled if enabled for all actions of selection:
			boolean enabled = true;
			for( Control c : selection ) {
				action.setTarget( c );
				if( !action.isEnabled() ) {
					enabled = false;
					break;
				}
			}
			return enabled; 
		}
		@Override
		public void run() {
			for( Control c: selection ) {
				action.setTarget( c );
				action.run();
			}
		}

		@Override
		public void actionChanged( IAction action ) {
			//action require update: transmit update request...
			//.. but don't want to be rewarned again if update fire update fire update ....
			((Action)action).removeActionListener( this );
			update();
			((Action)action).addActionListener( this );
		}
	}
	
	public void setFileName( String fn ) { filename = fn; }
	
	/** @return the scene file name, null for unsaveable scene (created programmatically) */
	public String getFileName() { return filename; }
	
	/** change the z order of controls, move first control to new index */
	public void changeZOrderOf( List<Control> list, int new_first_control_index )
	{
		controls.removeAll( list );
		int i = new_first_control_index;
		if( i<0 ) i=0; else if( i > controls.size()-1 ) i=controls.size()-1;
		for( Control c : list ) {
			controls.add( i, c );
			i++;
			c.redraw();
		}
	}

	@Override
	public Rectangle getBounds(Rectangle r) {
		return getBounds();
	}
	
}
